using UnityEngine;
using System.Collections;
using LogicalComponent.rules.grid;
using System.Linq;
using System.Text;
using System;
using System.Collections.Generic;
using LogicalComponent.util;
using LogicalComponent.userInterface;

public class Grid : MonoBehaviour, IGrid<Tile> {

    private GridLogic<Tile> gridLogic;
    public int Length;
    public int Width;
    public float TileSize;
    public Tile TemplateTile;
    public Tile[] Tiles;

    private ClickUtil.ClickMap clickMap;

    List<ITile> IGrid.AllTiles
    {
        get
        {
            return this.AllTiles.Cast<ITile>().ToList();
        }
    }

    public List<Tile> AllTiles
    {
        get
        {
            return gridLogic.AllTiles;
        }
    }

    [ContextMenu("Generate")]
    void Generate ()
    {
        TileFactory<Tile> factory = new SmartFactory(this);
        this.gridLogic = new GridLogic<Tile>(Width, Length, factory);
    }

    [ContextMenu("Clear")]
    void Clear()
    {
        foreach (Tile tile in this.Tiles)
        {
            DestroyImmediate(tile.gameObject);
        }
        this.Tiles = new Tile[0];
        this.gridLogic = null;
    }

    void Awake ()
    {
        if (this.gridLogic == null)
        {
            // This will create new tiles only if necessary
            this.Generate();
        }
        this.clickMap = new ClickUtil.ClickMap(this.transform.position.x,
                                               this.transform.position.z,
                                               this.TileSize);
    }

    ITile IGrid.GetTile(Coordinate c) { return this.GetTile(c); }

    public Tile GetTile(Coordinate c){
        Tile tile = this.gridLogic.GetTile(c);
        if (tile == null)
        {
            Debug.LogWarning(String.Format("{0} not found in Grid's gridLogic", c));
        }
        return this.gridLogic.GetTile(c);
    }

    ITile IGrid.GetTile(int x, int z) { return this.GetTile(x, z); }

    public Tile GetTile(int x, int z){
        return this.gridLogic.GetTile(new Coordinate(x, z));
    }

    public Coordinate GetClickCoordinate(RaycastHit hit)
    {
        Coordinate c = ClickUtil.GetClickCoordinate(hit.point.x, hit.point.z, this.clickMap);
        if (!gridLogic.IsOnGrid(c)){
            return null;
        }
        return c;
    }

    public void SetTile(int x, int z, Tile tile)
    {
        gridLogic.SetTile(x,z,tile);
    }

    public void SetTile(Coordinate coordinate, Tile tile)
    {
        gridLogic.SetTile(coordinate, tile);
    }

    public bool IsOnGrid(Coordinate coordinate)
    {
        return gridLogic.IsOnGrid(coordinate);
    }

    public bool IsOnGrid(int x, int z)
    {
        return gridLogic.IsOnGrid(x, z);
    }

    private class SmartFactory:TileFactory<Tile>{

        private Grid grid;

        public SmartFactory(Grid grid)
        {
            this.grid = grid;
        }

        public override Tile MakeTile (int x, int z)
        {
            Tile t = this.GetExistingTile(x, z);
            if (t == null)
            {
                t = this.MakeNewTile(x, z);
            }
            return t;
        }

        private Tile MakeNewTile(int x, int z)
        {
            Debug.Log(string.Format("Tile Created at {0}, {1}", x, z));
            Tile newTile;
            float buffer  = .5f * this.grid.TileSize;
            float xPos = this.grid.transform.position.x + buffer + x * this.grid.TileSize;
            float zPos = this.grid.transform.position.z + buffer + z * this.grid.TileSize;
            Vector3 position = new Vector3(xPos, this.grid.transform.position.y, zPos);
            newTile = (Tile)Instantiate(this.grid.TemplateTile,
                                        position,
                                        Quaternion.Euler(90,0,0));
            newTile.transform.parent = this.grid.transform;
            newTile.SetTileSize(this.grid.TileSize);
            int index = x * this.grid.Length + z;
            Tile tileComponent = newTile.GetComponent<Tile>();
            tileComponent.SpawnDefaultObject();
            this.grid.Tiles[index] = tileComponent;
            return tileComponent;
        }

        private Tile GetExistingTile(int x, int z)
        {
            if(this.grid.Width <= x || this.grid.Length <= z)
            {
                throw new Exception(String.Format("Coorinate ({0}, {1}) out of range", x, z));
            }
            int index = x * this.grid.Length + z;
            int length = this.grid.Length * this.grid.Width;
            if (this.grid.Tiles == null || this.grid.Tiles.Length < length)
            {
                this.grid.Tiles = new Tile[length];
            }
            return this.grid.Tiles[index];
        }
    }
}
